feat: implement dashboard infrastructure with new charts, sidebar com…#79
Conversation
…ponents, and custom color theme styling - charts, Integrations cards and integartion new route and dashboard backend logic needs to be work
📝 WalkthroughWalkthroughA comprehensive dashboard feature is introduced with backend API endpoints for overview metrics and execution trends, complemented by multiple client-side React components (stat cards, charts, gauges, integrations, recent workflows) orchestrated on a tabbed dashboard page. Supporting changes include an enhanced sidebar, refactored workflows page, new TypeScript types, Zod validation schemas, and unified green/dark theme styling across login, register, and input components. ChangesDashboard Feature Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (5)
apps/http-backend/src/routes/userRoutes/userRoutes.ts (1)
391-392: 💤 Low value
req.query.rangecan be astring[]in Express when the param is repeated.If a caller sends
?range=7d&range=30d, Express parsesreq.query.rangeas["7d", "30d"]. The??fallback only catchesundefined, so an array would reachDashboardRangeSchema.safeParseand correctly fail with a 400. This is safe, but if you want to be explicit:-const rangeInput = req.query.range ?? "7d"; +const rangeInput = typeof req.query.range === "string" ? req.query.range : "7d";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/http-backend/src/routes/userRoutes/userRoutes.ts` around lines 391 - 392, req.query.range may be a string[] when the param is repeated, so ensure rangeInput is always a string before passing to DashboardRangeSchema.safeParse; update the code around rangeInput/parsedRange to coerce arrays to a single value (e.g., Array.isArray(req.query.range) ? req.query.range[0] : req.query.range) and then apply the existing "?? '7d'" fallback, keeping the subsequent use of DashboardRangeSchema.safeParse(rangeInput) unchanged.apps/web/app/components/ui/Inputbox.tsx (1)
28-67: 💤 Low valueTheme update looks consistent with the rest of the PR.
One minor note: the
animate-pulseclass on the error span (Line 67) triggers a continuous animation. Users with vestibular disorders may be affected. Consider applying it conditionally (e.g., only on initial render) or usingmotion-safe:animate-pulseto respect the OSprefers-reduced-motionsetting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/components/ui/Inputbox.tsx` around lines 28 - 67, The error span in the Inputbox component currently uses "animate-pulse" which forces continuous motion; change this to respect users' reduced-motion preference by replacing the class with "motion-safe:animate-pulse" on the error <span> (the span rendered when error is truthy), or alternatively add a short-lived mounted state (e.g., in the Inputbox component) to apply "animate-pulse" only on initial render and then remove it — update the span's className logic to reference that state or the motion-safe utility while keeping the rest of the error rendering intact.apps/web/app/components/dashboard/WorkflowActivityChart.tsx (1)
98-105: ⚡ Quick winHardcoded SVG gradient IDs will conflict if this component is ever rendered more than once per page.
id="greenGradient"andid="grayGradient"are global within the SVG/DOM. If a secondWorkflowActivityChartinstance is mounted (e.g., in a comparison view, a modal, or a unit test), bothurl(#greenGradient)references point to the first gradient definition, breaking the fill colors of all subsequent instances.Use a per-instance unique suffix (e.g., via
useIdfrom React 18+):♻️ Proposed fix
+import { useMemo, useId } from "react"; ... + const uid = useId(); + const greenId = `greenGradient-${uid}`; + const grayId = `grayGradient-${uid}`; ... - <linearGradient id="greenGradient" x1="0" y1="0" x2="0" y2="1"> + <linearGradient id={greenId} x1="0" y1="0" x2="0" y2="1"> ... - <linearGradient id="grayGradient" x1="0" y1="0" x2="0" y2="1"> + <linearGradient id={grayId} x1="0" y1="0" x2="0" y2="1"> ... - <Area ... fill="url(`#greenGradient`)" /> - <Area ... fill="url(`#grayGradient`)" /> + <Area ... fill={`url(#${greenId})`} /> + <Area ... fill={`url(#${grayId})`} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/components/dashboard/WorkflowActivityChart.tsx` around lines 98 - 105, The SVG gradient IDs in WorkflowActivityChart (greenGradient and grayGradient) are hardcoded and will collide across multiple mounted instances; update the component to generate per-instance unique IDs (e.g., via React's useId or another unique suffix) and replace the literal id attributes and any corresponding url(`#greenGradient`)/url(`#grayGradient`) references with the generated ids so each chart instance references its own gradients.apps/web/app/lib/api.ts (1)
115-115: ⚡ Quick winImport and reuse
DashboardRangeinstead of duplicating the inline literal type.The parameter type
"7d" | "30d" | "90d"is a verbatim copy ofDashboardRangedefined indashboard.types.ts. This creates a silent divergence risk if the range options ever change.♻️ Proposed refactor
+import { DashboardRange } from "@/app/types/dashboard.types"; ... - getExecutionTrend: async (range: "7d" | "30d" | "90d" = "7d") => { + getExecutionTrend: async (range: DashboardRange = "7d") => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/lib/api.ts` at line 115, Replace the inline literal type on the getExecutionTrend parameter with the shared DashboardRange type: import DashboardRange from the module where it's declared (dashboard.types.ts) and change the signature from getExecutionTrend: async (range: "7d" | "30d" | "90d" = "7d") => to getExecutionTrend: async (range: DashboardRange = "7d") =>; ensure the import name matches the exported symbol (DashboardRange) and update any related usages or tests to use the unified type.apps/web/app/dashboard/page.tsx (1)
72-97: ⚡ Quick winInflight fetch requests are not cancelled on component unmount.
fetchOverviewandfetchTrendhave no cleanup path. If the user navigates away while either fetch is still in-flight, thesetStatecalls insidefinally/catchwill execute on an unmounted component (React 18 no-ops them, but the requests keep running and the associated closures are retained until settlement). The fix is to thread anAbortSignalthrough each fetch and return a cleanup function from eachuseEffect:♻️ Proposed fix
- const fetchOverview = async () => { + const fetchOverview = async (signal?: AbortSignal) => { setOverviewLoading(true); setOverviewError(null); try { - setOverview(await api.dashboard.getOverview()); + setOverview(await api.dashboard.getOverview(signal)); } catch (e: any) { + if (e?.name === "AbortError") return; setOverviewError(e?.message || "Failed to load overview"); } finally { setOverviewLoading(false); } }; - const fetchTrend = async (range: DashboardRange) => { + const fetchTrend = async (range: DashboardRange, signal?: AbortSignal) => { setTrendLoading(true); setTrendError(null); try { - setTrend(await api.dashboard.getExecutionTrend(range)); + setTrend(await api.dashboard.getExecutionTrend(range, signal)); } catch (e: any) { + if (e?.name === "AbortError") return; setTrendError(e?.message || "Failed to load trend"); } finally { setTrendLoading(false); } }; - useEffect(() => { fetchOverview(); }, []); - useEffect(() => { fetchTrend(selectedRange); }, [selectedRange]); + useEffect(() => { + const controller = new AbortController(); + fetchOverview(controller.signal); + return () => controller.abort(); + }, []); + + useEffect(() => { + const controller = new AbortController(); + fetchTrend(selectedRange, controller.signal); + return () => controller.abort(); + }, [selectedRange]);
api.dashboard.getOverviewandapi.dashboard.getExecutionTrendwould need to forward the signal to their underlyingfetchcalls.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/dashboard/page.tsx` around lines 72 - 97, fetchOverview and fetchTrend lack cancellation which lets in-flight requests continue after unmount; update both functions to accept and forward an AbortSignal to api.dashboard.getOverview and api.dashboard.getExecutionTrend (ensure those functions pass the signal to their underlying fetch), create an AbortController inside each useEffect, call the respective fetch with controller.signal, and return a cleanup from each useEffect that calls controller.abort(); also handle aborted errors in the catch paths of fetchOverview/fetchTrend to avoid treating abort as a real error (check error.name === "AbortError" or similar) while keeping setState calls only for non-aborted results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/http-backend/src/routes/userRoutes/userRoutes.ts`:
- Around line 303-318: The current prismaClient.workflowExecution.findMany call
loads all executions into memory and then filters with isTestingExecution,
causing unbounded growth; add an isTest boolean column to the workflowExecution
model and change the aggregate logic (e.g., use
prismaClient.workflowExecution.count with where: { workflow: { userId }, isTest:
false } and status filters) so counts are computed server-side, or as a
short-term mitigation add a take cap and appropriate ordering on
prismaClient.workflowExecution.findMany and document the approximation; update
any other usages (e.g., the trend endpoint's findMany) similarly to avoid
unbounded result sets.
In `@apps/web/app/components/dashboard/DashboardSidebar.tsx`:
- Line 155: The hardcoded personal email in DashboardSidebar.tsx (the onClick
handler on the upgrade button) should be replaced with a product/team address
stored as a config constant or environment variable; update the onClick in the
button (the anonymous function that sets window.location.href) to reference that
constant (e.g., UPGRADE_CONTACT_EMAIL or config.upgradeEmail) and ensure the
mailto subject remains, then load that constant from a safe config/env module so
the personal address is removed from the client bundle.
In `@apps/web/app/components/dashboard/DashboardStatCard.tsx`:
- Line 7: The type annotation uses the React namespace ("icon: React.ReactNode")
but React isn't imported, so add an import to fix the TS error; either import
the namespace (import React from 'react') or import the type directly (import
type { ReactNode } from 'react' and change the annotation to "icon: ReactNode")
— update the DashboardStatCard component's prop type where "icon:
React.ReactNode" appears to use the imported symbol.
In `@apps/web/app/components/dashboard/ExecutionHealthGauge.tsx`:
- Around line 30-32: The icon-only button in ExecutionHealthGauge.tsx using the
<MoreHorizontal /> icon is missing an accessible label; update the <button>
element to include an aria-label (or aria-labelledby) with a concise description
such as "More options" and/or add visually hidden text inside the button for
screen readers so assistive tech announces its purpose; ensure the change is
applied to the button element that currently wraps <MoreHorizontal /> and keep
existing classes and type attribute intact.
- Around line 17-21: The gaugeData construction in ExecutionHealthGauge
currently uses a fallback of failedRate || 0.5 which shows a misleading red arc
when failedRate is 0; change gaugeData to use an explicit check for zero and
either omit the Failed slice entirely or use a very small epsilon (e.g., 0.001)
purely for visual spacing and conditionally hide its legend/label so the center
percentage remains accurate; update the array creation in the gaugeData useMemo
and any legend-rendering logic to skip / hide the failed entry when failedRate
=== 0.
In `@apps/web/app/components/dashboard/IntegrationStatus.tsx`:
- Around line 14-17: The iconMap entry for the googleSheets integration in
IntegrationStatus.tsx is mislabeled; update the iconMap Record (key:
googleSheets) to change its label from "Google Drive" to "Google Sheets" so it
matches the backend and renders correctly in the UI (locate the iconMap constant
near the top of the component).
In `@apps/web/app/components/dashboard/RecentWorkflows.tsx`:
- Line 81: The "Duration" column header in RecentWorkflows.tsx is misleading
because the cell value comes from getTimeDuration(wf.createdAt) (relative age),
so change the header text from "Duration" to "Age" (or "Created") and update any
related accessibility labels/titles/tooltip strings for that <th> element to
match (e.g., the <th> that currently contains "Duration" and any aria-labels or
tests that reference it); ensure the display and semantics now align with
getTimeDuration(wf.createdAt).
In `@apps/web/app/components/dashboard/WorkflowActivityChart.tsx`:
- Line 62: The static "CRM" label in WorkflowActivityChart.tsx is a stale
placeholder and should be removed or replaced with a meaningful label; locate
the span rendering the text (the <span className="text-xs
text-[`#5a6350`]">CRM</span> near the peak completed count) and either delete that
span or change its text to a contextual string like "Peak" (or another suitable
term), keeping the existing styling and layout so only the label content is
updated.
- Line 37: The X-axis label generation in WorkflowActivityChart (the label
property in the data mapping) uses .charAt(0) which collapses distinct weekdays
into identical single letters; update the label expression to use the full
3-letter abbreviation returned by toLocaleDateString (e.g., new
Date(p.date).toLocaleDateString("en-US", { weekday: "short" })) instead of
slicing to a single character so Tuesday/Thursday and Saturday/Sunday remain
distinguishable.
In `@packages/ui/src/styles/globals.css`:
- Line 2: Replace the `@import` url(...) statement in globals.css with the string
notation required by Stylelint: change the current `@import`
url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
to the string form `@import`
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap';
so the import-notation rule is satisfied.
- Around line 148-171: Rename the keyframe from fadeInUp to kebab-case (e.g.,
fade-in-up) and update the animation reference in the .animate-fade-in-up
utility to use the new name (animation: fade-in-up 0.5s ease-out forwards);
ensure the `@keyframes` rule and any other places referencing fadeInUp are changed
to the new kebab-case identifier so it satisfies the keyframes-name-pattern
rule.
- Around line 126-128: The font-family declaration after the `@apply` rule in the
global CSS violates Stylelint: add a blank line before the font-family property
and remove the quotes around Inter (use Inter without quotes) so the declaration
reads an unquoted Inter followed by the existing fallbacks; update the rule near
the `@apply` bg-background text-foreground block (the font-family line) to satisfy
declaration-empty-line-before and font-family-name-quotes.
---
Nitpick comments:
In `@apps/http-backend/src/routes/userRoutes/userRoutes.ts`:
- Around line 391-392: req.query.range may be a string[] when the param is
repeated, so ensure rangeInput is always a string before passing to
DashboardRangeSchema.safeParse; update the code around rangeInput/parsedRange to
coerce arrays to a single value (e.g., Array.isArray(req.query.range) ?
req.query.range[0] : req.query.range) and then apply the existing "?? '7d'"
fallback, keeping the subsequent use of
DashboardRangeSchema.safeParse(rangeInput) unchanged.
In `@apps/web/app/components/dashboard/WorkflowActivityChart.tsx`:
- Around line 98-105: The SVG gradient IDs in WorkflowActivityChart
(greenGradient and grayGradient) are hardcoded and will collide across multiple
mounted instances; update the component to generate per-instance unique IDs
(e.g., via React's useId or another unique suffix) and replace the literal id
attributes and any corresponding url(`#greenGradient`)/url(`#grayGradient`)
references with the generated ids so each chart instance references its own
gradients.
In `@apps/web/app/components/ui/Inputbox.tsx`:
- Around line 28-67: The error span in the Inputbox component currently uses
"animate-pulse" which forces continuous motion; change this to respect users'
reduced-motion preference by replacing the class with
"motion-safe:animate-pulse" on the error <span> (the span rendered when error is
truthy), or alternatively add a short-lived mounted state (e.g., in the Inputbox
component) to apply "animate-pulse" only on initial render and then remove it —
update the span's className logic to reference that state or the motion-safe
utility while keeping the rest of the error rendering intact.
In `@apps/web/app/dashboard/page.tsx`:
- Around line 72-97: fetchOverview and fetchTrend lack cancellation which lets
in-flight requests continue after unmount; update both functions to accept and
forward an AbortSignal to api.dashboard.getOverview and
api.dashboard.getExecutionTrend (ensure those functions pass the signal to their
underlying fetch), create an AbortController inside each useEffect, call the
respective fetch with controller.signal, and return a cleanup from each
useEffect that calls controller.abort(); also handle aborted errors in the catch
paths of fetchOverview/fetchTrend to avoid treating abort as a real error (check
error.name === "AbortError" or similar) while keeping setState calls only for
non-aborted results.
In `@apps/web/app/lib/api.ts`:
- Line 115: Replace the inline literal type on the getExecutionTrend parameter
with the shared DashboardRange type: import DashboardRange from the module where
it's declared (dashboard.types.ts) and change the signature from
getExecutionTrend: async (range: "7d" | "30d" | "90d" = "7d") => to
getExecutionTrend: async (range: DashboardRange = "7d") =>; ensure the import
name matches the exported symbol (DashboardRange) and update any related usages
or tests to use the unified type.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a0dff55c-9a1b-40f6-8919-3878ce628cf8
📒 Files selected for processing (19)
apps/http-backend/src/routes/userRoutes/userRoutes.tsapps/web/app/components/ExecutionHistoryFooter.tsxapps/web/app/components/dashboard/DashboardSidebar.tsxapps/web/app/components/dashboard/DashboardStatCard.tsxapps/web/app/components/dashboard/ExecutionHealthGauge.tsxapps/web/app/components/dashboard/IntegrationStatus.tsxapps/web/app/components/dashboard/RecentWorkflows.tsxapps/web/app/components/dashboard/WorkflowActivityChart.tsxapps/web/app/components/ui/Inputbox.tsxapps/web/app/components/ui/app-sidebar.tsxapps/web/app/components/workflows/WorkflowListView.tsxapps/web/app/dashboard/page.tsxapps/web/app/lib/api.tsapps/web/app/login/page.tsxapps/web/app/register/page.tsxapps/web/app/types/dashboard.types.tsapps/web/app/workflows/page.tsxpackages/common/src/index.tspackages/ui/src/styles/globals.css
| prismaClient.workflowExecution.findMany({ | ||
| where: { | ||
| workflow: { | ||
| userId, | ||
| }, | ||
| }, | ||
| select: { | ||
| status: true, | ||
| metadata: true, | ||
| }, | ||
| }), | ||
| ]); | ||
|
|
||
| const executions = executionRows.filter( | ||
| (execution) => !isTestingExecution(execution.metadata) | ||
| ); |
There was a problem hiding this comment.
Unbounded findMany loads all historical executions into memory to compute statistics.
workflowExecution.findMany has no take limit and fetches every execution record ever created for the user's workflows. This grows without bound over time. The in-memory isTestingExecution filter prevents a direct groupBy, but there are two practical paths forward:
Option A (recommended): Add an isTest boolean column to workflowExecution at the schema level. This allows the statistics to be computed with cheap server-side aggregates:
// After schema migration
const [executionCount, failedCount, completedCount] = await Promise.all([
prismaClient.workflowExecution.count({
where: { workflow: { userId }, isTest: false },
}),
prismaClient.workflowExecution.count({
where: { workflow: { userId }, isTest: false, status: "Failed" },
}),
prismaClient.workflowExecution.count({
where: { workflow: { userId }, isTest: false, status: "Completed" },
}),
]);Option B (quick mitigation): Apply a take cap until the schema is updated, and acknowledge the approximation:
prismaClient.workflowExecution.findMany({
where: { workflow: { userId } },
select: { status: true, metadata: true },
+ take: 5000, // temporary safety cap; replace with DB-side aggregation
+ orderBy: { startAt: "desc" },
}),The same concern applies to the trend endpoint's findMany at Line 407, though the date-range filter bounds its growth to the selected window.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/http-backend/src/routes/userRoutes/userRoutes.ts` around lines 303 -
318, The current prismaClient.workflowExecution.findMany call loads all
executions into memory and then filters with isTestingExecution, causing
unbounded growth; add an isTest boolean column to the workflowExecution model
and change the aggregate logic (e.g., use prismaClient.workflowExecution.count
with where: { workflow: { userId }, isTest: false } and status filters) so
counts are computed server-side, or as a short-term mitigation add a take cap
and appropriate ordering on prismaClient.workflowExecution.findMany and document
the approximation; update any other usages (e.g., the trend endpoint's findMany)
similarly to avoid unbounded result sets.
| <span className="text-[11px] text-[#5a6350] truncate">{user.email || 'user@example.com'}</span> | ||
| </div> | ||
| </div> | ||
| <button onClick={() => { window.location.href = 'mailto:tejabudumuru3@gmail.com?subject=BuildFlow-Upgrade' }} className="mt-4 w-full py-2 rounded-lg bg-[#baf266]/10 text-[#baf266] text-xs font-semibold border border-[#baf266]/20 hover:bg-[#baf266]/20 transition-all group-data-[state=collapsed]:hidden"> |
There was a problem hiding this comment.
Personal email address hardcoded in client-side production code.
tejabudumuru3@gmail.com is shipped to every user in the JS bundle and points directly to a personal mailbox, which is both a PII exposure and an inappropriate upgrade flow mechanism. Replace with a product/team address and consider moving it to an environment variable or config constant.
🔧 Proposed fix
-<button onClick={() => { window.location.href = 'mailto:tejabudumuru3@gmail.com?subject=BuildFlow-Upgrade' }} ...>
+<button onClick={() => { window.location.href = `mailto:${process.env.NEXT_PUBLIC_SUPPORT_EMAIL ?? 'support@buildflow.io'}?subject=BuildFlow-Upgrade` }} ...>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button onClick={() => { window.location.href = 'mailto:tejabudumuru3@gmail.com?subject=BuildFlow-Upgrade' }} className="mt-4 w-full py-2 rounded-lg bg-[#baf266]/10 text-[#baf266] text-xs font-semibold border border-[#baf266]/20 hover:bg-[#baf266]/20 transition-all group-data-[state=collapsed]:hidden"> | |
| <button onClick={() => { window.location.href = `mailto:${process.env.NEXT_PUBLIC_SUPPORT_EMAIL ?? 'support@buildflow.io'}?subject=BuildFlow-Upgrade` }} className="mt-4 w-full py-2 rounded-lg bg-[`#baf266`]/10 text-[`#baf266`] text-xs font-semibold border border-[`#baf266`]/20 hover:bg-[`#baf266`]/20 transition-all group-data-[state=collapsed]:hidden"> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/components/dashboard/DashboardSidebar.tsx` at line 155, The
hardcoded personal email in DashboardSidebar.tsx (the onClick handler on the
upgrade button) should be replaced with a product/team address stored as a
config constant or environment variable; update the onClick in the button (the
anonymous function that sets window.location.href) to reference that constant
(e.g., UPGRADE_CONTACT_EMAIL or config.upgradeEmail) and ensure the mailto
subject remains, then load that constant from a safe config/env module so the
personal address is removed from the client bundle.
|
|
||
| interface StatCardProps { | ||
| border: boolean; | ||
| icon: React.ReactNode; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify: search for other files using React.ReactNode without a React import
rg --type=ts --type=tsx -n "React\.ReactNode" -l | xargs grep -L "import React"Repository: Dev-Pross/BuildFlow
Length of output: 128
🏁 Script executed:
# Check if the file exists and read its content
cat -n apps/web/app/components/dashboard/DashboardStatCard.tsxRepository: Dev-Pross/BuildFlow
Length of output: 1902
🏁 Script executed:
# Check TypeScript configuration
cat apps/web/tsconfig.jsonRepository: Dev-Pross/BuildFlow
Length of output: 521
🏁 Script executed:
# Find the workspace typescript config
fd -type f -name "*.json" | xargs grep -l "typescript-config/nextjs" | head -5Repository: Dev-Pross/BuildFlow
Length of output: 234
🏁 Script executed:
# Look for the actual nextjs.json config
find . -path "*/typescript-config/nextjs.json" -o -path "*/typescript-config/*nextjs*"Repository: Dev-Pross/BuildFlow
Length of output: 104
🏁 Script executed:
# Check if React is re-exported or provided globally
rg -i "react" packages/*/tsconfig.json --type jsonRepository: Dev-Pross/BuildFlow
Length of output: 153
🏁 Script executed:
# Read the workspace nextjs.json config
cat packages/typescript-config/nextjs.jsonRepository: Dev-Pross/BuildFlow
Length of output: 359
🏁 Script executed:
# Read the base config
cat packages/typescript-config/base.jsonRepository: Dev-Pross/BuildFlow
Length of output: 587
React.ReactNode used without importing React — TypeScript compilation error.
The React namespace is referenced in the type annotation on line 7, but React is never imported. The new JSX transform (React 17+) only removes the need for the JSX factory call; accessing React.ReactNode as a namespace still requires an explicit import in module-scope TypeScript files. With strict: true enabled in tsconfig, this will cause a compilation failure.
🔧 Proposed fix
"use client";
+import type { ReactNode } from "react";
import { TrendingUp, TrendingDown } from "lucide-react";
interface StatCardProps {
border: boolean;
- icon: React.ReactNode;
+ icon: ReactNode;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| icon: React.ReactNode; | |
| "use client"; | |
| import type { ReactNode } from "react"; | |
| import { TrendingUp, TrendingDown } from "lucide-react"; | |
| interface StatCardProps { | |
| border: boolean; | |
| icon: ReactNode; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/components/dashboard/DashboardStatCard.tsx` at line 7, The type
annotation uses the React namespace ("icon: React.ReactNode") but React isn't
imported, so add an import to fix the TS error; either import the namespace
(import React from 'react') or import the type directly (import type { ReactNode
} from 'react' and change the annotation to "icon: ReactNode") — update the
DashboardStatCard component's prop type where "icon: React.ReactNode" appears to
use the imported symbol.
| const gaugeData = useMemo(() => [ | ||
| { name: "Success", value: successRate, color: "#baf266" }, | ||
| { name: "Delayed", value: delayedRate, color: "#f59e0b" }, | ||
| { name: "Failed", value: failedRate || 0.5, color: "#f87171" }, | ||
| ], [successRate, failedRate, delayedRate]); |
There was a problem hiding this comment.
failedRate || 0.5 shows a misleading non-zero "Failed" arc when there are zero failures.
When failedRate is 0, the gauge renders a visible red slice anyway, giving users the false impression that executions have failed. If the intent is purely cosmetic (ensure the arc is never invisible), the phantom value should at minimum not affect the center percentage and ideally not appear in the chart itself. Prefer an explicit check:
🐛 Proposed fix
-{ name: "Failed", value: failedRate || 0.5, color: "#f87171" },
+{ name: "Failed", value: failedRate, color: "#f87171" },If a visible arc is truly required when failedRate is 0 for aesthetic spacing, use a vanishingly small value and hide the legend entry conditionally — but surfacing 0.5% as a real data point is the more damaging choice.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const gaugeData = useMemo(() => [ | |
| { name: "Success", value: successRate, color: "#baf266" }, | |
| { name: "Delayed", value: delayedRate, color: "#f59e0b" }, | |
| { name: "Failed", value: failedRate || 0.5, color: "#f87171" }, | |
| ], [successRate, failedRate, delayedRate]); | |
| const gaugeData = useMemo(() => [ | |
| { name: "Success", value: successRate, color: "#baf266" }, | |
| { name: "Delayed", value: delayedRate, color: "#f59e0b" }, | |
| { name: "Failed", value: failedRate, color: "#f87171" }, | |
| ], [successRate, failedRate, delayedRate]); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/components/dashboard/ExecutionHealthGauge.tsx` around lines 17 -
21, The gaugeData construction in ExecutionHealthGauge currently uses a fallback
of failedRate || 0.5 which shows a misleading red arc when failedRate is 0;
change gaugeData to use an explicit check for zero and either omit the Failed
slice entirely or use a very small epsilon (e.g., 0.001) purely for visual
spacing and conditionally hide its legend/label so the center percentage remains
accurate; update the array creation in the gaugeData useMemo and any
legend-rendering logic to skip / hide the failed entry when failedRate === 0.
| <button className="p-1 rounded-md hover:bg-[#1a2118] text-[#5a6350] transition-colors" type="button"> | ||
| <MoreHorizontal className="h-4 w-4" /> | ||
| </button> |
There was a problem hiding this comment.
Icon-only button is missing an accessible label.
<MoreHorizontal /> provides no text for assistive technologies; the button is announced as an unnamed button to screen readers.
♿ Proposed fix
-<button className="p-1 rounded-md hover:bg-[`#1a2118`] text-[`#5a6350`] transition-colors" type="button">
+<button className="p-1 rounded-md hover:bg-[`#1a2118`] text-[`#5a6350`] transition-colors" type="button" aria-label="More options">
<MoreHorizontal className="h-4 w-4" />
</button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button className="p-1 rounded-md hover:bg-[#1a2118] text-[#5a6350] transition-colors" type="button"> | |
| <MoreHorizontal className="h-4 w-4" /> | |
| </button> | |
| <button className="p-1 rounded-md hover:bg-[`#1a2118`] text-[`#5a6350`] transition-colors" type="button" aria-label="More options"> | |
| <MoreHorizontal className="h-4 w-4" /> | |
| </button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/components/dashboard/ExecutionHealthGauge.tsx` around lines 30 -
32, The icon-only button in ExecutionHealthGauge.tsx using the <MoreHorizontal
/> icon is missing an accessible label; update the <button> element to include
an aria-label (or aria-labelledby) with a concise description such as "More
options" and/or add visually hidden text inside the button for screen readers so
assistive tech announces its purpose; ensure the change is applied to the button
element that currently wraps <MoreHorizontal /> and keep existing classes and
type attribute intact.
| if (!trend?.points) return []; | ||
| return trend.points.map((p) => ({ | ||
| ...p, | ||
| label: new Date(p.date).toLocaleDateString("en-US", { weekday: "short" }).charAt(0), |
There was a problem hiding this comment.
Single-character weekday labels produce ambiguous X-axis tick collisions.
charAt(0) produces "T" for both Tuesday and Thursday, and "S" for both Saturday and Sunday. In the 7-day view all 7 bars would be shown, making Tuesday/Thursday and Saturday/Sunday indistinguishable.
Use the 3-letter abbreviation directly instead:
🐛 Proposed fix
- label: new Date(p.date).toLocaleDateString("en-US", { weekday: "short" }).charAt(0),
+ label: new Date(p.date).toLocaleDateString("en-US", { weekday: "short" }),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| label: new Date(p.date).toLocaleDateString("en-US", { weekday: "short" }).charAt(0), | |
| label: new Date(p.date).toLocaleDateString("en-US", { weekday: "short" }), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/components/dashboard/WorkflowActivityChart.tsx` at line 37, The
X-axis label generation in WorkflowActivityChart (the label property in the data
mapping) uses .charAt(0) which collapses distinct weekdays into identical single
letters; update the label expression to use the full 3-letter abbreviation
returned by toLocaleDateString (e.g., new
Date(p.date).toLocaleDateString("en-US", { weekday: "short" })) instead of
slicing to a single character so Tuesday/Thursday and Saturday/Sunday remain
distinguishable.
| {/* Peak indicator */} | ||
| {peakValue.value > 0 && ( | ||
| <div className="flex items-center gap-3 mb-3"> | ||
| <span className="text-xs text-[#5a6350]">CRM</span> |
There was a problem hiding this comment.
"CRM" label appears to be stale placeholder text — wrong domain context.
The peak indicator renders "CRM" as a static prefix beside the peak completed count. This is a workflow automation dashboard, not a CRM; the label has no semantic meaning here and will confuse users. Either remove it or replace it with something meaningful (e.g., "Peak").
🐛 Proposed fix
- <span className="text-xs text-[`#5a6350`]">CRM</span>
+ <span className="text-xs text-[`#5a6350`]">Peak</span>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span className="text-xs text-[#5a6350]">CRM</span> | |
| <span className="text-xs text-[`#5a6350`]">Peak</span> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/components/dashboard/WorkflowActivityChart.tsx` at line 62, The
static "CRM" label in WorkflowActivityChart.tsx is a stale placeholder and
should be removed or replaced with a meaningful label; locate the span rendering
the text (the <span className="text-xs text-[`#5a6350`]">CRM</span> near the peak
completed count) and either delete that span or change its text to a contextual
string like "Peak" (or another suitable term), keeping the existing styling and
layout so only the label content is updated.
| @@ -1,4 +1,5 @@ | |||
| @import "tailwindcss"; | |||
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); | |||
There was a problem hiding this comment.
Stylelint import-notation violation: use string notation, not url().
Stylelint requires CSS imports to use the string form.
🔧 Proposed fix
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
+@import 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); | |
| `@import` 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'; |
🧰 Tools
🪛 Stylelint (17.9.0)
[error] 2-2: Expected "url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap')" to be "'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'" (import-notation)
(import-notation)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/ui/src/styles/globals.css` at line 2, Replace the `@import` url(...)
statement in globals.css with the string notation required by Stylelint: change
the current `@import`
url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
to the string form `@import`
'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap';
so the import-notation rule is satisfied.
| @apply bg-background text-foreground; | ||
| font-family: 'Inter', system-ui, -apple-system, sans-serif; | ||
| } |
There was a problem hiding this comment.
Two Stylelint violations on the font-family declaration.
declaration-empty-line-before: a blank line is required before the property when it follows an@applyrule.font-family-name-quotes:Interis not a CSS keyword and must be unquoted.
🔧 Proposed fix
body {
`@apply` bg-background text-foreground;
- font-family: 'Inter', system-ui, -apple-system, sans-serif;
+
+ font-family: Inter, system-ui, -apple-system, sans-serif;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @apply bg-background text-foreground; | |
| font-family: 'Inter', system-ui, -apple-system, sans-serif; | |
| } | |
| `@apply` bg-background text-foreground; | |
| font-family: Inter, system-ui, -apple-system, sans-serif; | |
| } |
🧰 Tools
🪛 Stylelint (17.9.0)
[error] 127-127: Expected empty line before declaration (declaration-empty-line-before)
(declaration-empty-line-before)
[error] 127-127: Expected no quotes around "Inter" (font-family-name-quotes)
(font-family-name-quotes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/ui/src/styles/globals.css` around lines 126 - 128, The font-family
declaration after the `@apply` rule in the global CSS violates Stylelint: add a
blank line before the font-family property and remove the quotes around Inter
(use Inter without quotes) so the declaration reads an unquoted Inter followed
by the existing fallbacks; update the rule near the `@apply` bg-background
text-foreground block (the font-family line) to satisfy
declaration-empty-line-before and font-family-name-quotes.
| @keyframes fadeInUp { | ||
| from { | ||
| opacity: 0; | ||
| transform: translateY(12px); | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| transform: translateY(0); | ||
| } | ||
| } | ||
|
|
||
| @keyframes shimmer { | ||
| 0% { background-position: -200% 0; } | ||
| 100% { background-position: 200% 0; } | ||
| } | ||
|
|
||
| @keyframes pulse-glow { | ||
| 0%, 100% { box-shadow: 0 0 20px rgba(186, 242, 102, 0.05); } | ||
| 50% { box-shadow: 0 0 30px rgba(186, 242, 102, 0.12); } | ||
| } | ||
|
|
||
| .animate-fade-in-up { | ||
| animation: fadeInUp 0.5s ease-out forwards; | ||
| } |
There was a problem hiding this comment.
fadeInUp violates the keyframes-name-pattern (kebab-case) rule; update the animation reference too.
The Stylelint keyframes-name-pattern rule requires kebab-case. Renaming the keyframe also requires updating the animation shorthand in the utility class.
🔧 Proposed fix
-@keyframes fadeInUp {
+@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
- animation: fadeInUp 0.5s ease-out forwards;
+ animation: fade-in-up 0.5s ease-out forwards;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(12px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes shimmer { | |
| 0% { background-position: -200% 0; } | |
| 100% { background-position: 200% 0; } | |
| } | |
| @keyframes pulse-glow { | |
| 0%, 100% { box-shadow: 0 0 20px rgba(186, 242, 102, 0.05); } | |
| 50% { box-shadow: 0 0 30px rgba(186, 242, 102, 0.12); } | |
| } | |
| .animate-fade-in-up { | |
| animation: fadeInUp 0.5s ease-out forwards; | |
| } | |
| `@keyframes` fade-in-up { | |
| from { | |
| opacity: 0; | |
| transform: translateY(12px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| `@keyframes` shimmer { | |
| 0% { background-position: -200% 0; } | |
| 100% { background-position: 200% 0; } | |
| } | |
| `@keyframes` pulse-glow { | |
| 0%, 100% { box-shadow: 0 0 20px rgba(186, 242, 102, 0.05); } | |
| 50% { box-shadow: 0 0 30px rgba(186, 242, 102, 0.12); } | |
| } | |
| .animate-fade-in-up { | |
| animation: fade-in-up 0.5s ease-out forwards; | |
| } |
🧰 Tools
🪛 Stylelint (17.9.0)
[error] 148-148: Expected keyframe name "fadeInUp" to be kebab-case (keyframes-name-pattern)
(keyframes-name-pattern)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/ui/src/styles/globals.css` around lines 148 - 171, Rename the
keyframe from fadeInUp to kebab-case (e.g., fade-in-up) and update the animation
reference in the .animate-fade-in-up utility to use the new name (animation:
fade-in-up 0.5s ease-out forwards); ensure the `@keyframes` rule and any other
places referencing fadeInUp are changed to the new kebab-case identifier so it
satisfies the keyframes-name-pattern rule.
…ponents, and custom color theme styling
Summary by CodeRabbit
Release Notes
New Features
UI Updates